﻿using Sasa.QualityTools.CodeAnalysis.Core.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Sasa.QualityTools.CodeAnalysis.Core.Utilities;
using Microsoft.Win32;
using System.Reflection;
using System.Diagnostics;
using Sasa.QualityTools.CodeAnalysis.Common;
using System.Globalization;

namespace Sasa.QualityTools.CodeAnalysis.Cli
{
    public class Program
    {
        private const string RegistryKeyName = @"HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0\DialogPage\Sasa.QualityTools.CodeAnalysis.FxCopIntegrator.Options.GeneralOptionPageData";

        private const string RegistryValueName = "FxCopCmdPath";

        private static readonly string Help = String.Join(Environment.NewLine,
            "Usage: CodeAnalysis.exe /f:<path> /s:<path> /p:<path> [/d:<path> [...]]",
            "",
            "More help on command line options",
            "",
            "/f:<path>",
            "Assembly path to analyze.",
            "",
            "/s:<path>",
            "Solution directory.",
            "",
            "/p:<path>",
            "Project directory.",
            "",
            "/d:<path>",
            "Location of search for assembly dependencies. This is optional.");

        private static readonly TraceSource logger = new TraceSource("logger");


        public Solution Solution { get; private set; }

        public Project Project { get; private set; }


        public int Execute(string[] args)
        {
            Solution = BuildUpSolution(args);
            Project = BuildUpProject(args);
            Project.Solution = Solution;

            var registryValue = Registry.GetValue(RegistryKeyName, RegistryValueName, null);
            if (registryValue == null)
            {
                var message = Text.Format("The FxCopCmd path can't be retrieved from the registry ({0}\\{1}).", RegistryKeyName, RegistryValueName);
                throw new ExecutionException(message);
            }

            var command = Convert.ToString(registryValue, CultureInfo.InvariantCulture);
            if (!File.Exists(command))
            {
                var message = Text.Format("The specified FxCopCmd ({0}) doesn't exist.", command);
                throw new ExecutionException(message);
            }

            var version = Assembly.LoadFile(command).GetName().Version;
            var option = FxCopCmdOptionBuilder.Build(Project, version);

            var process = new Process();
            process.StartInfo = new ProcessStartInfo(command, option);
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.CreateNoWindow = true;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardError = true;

            process.OutputDataReceived += (sender, e) =>
            {
                Console.WriteLine(e.Data);
                Logger.Info("FxCopCmd: {0}", e.Data);
            };
            process.ErrorDataReceived += (sender, e) =>
            {
                Console.Error.WriteLine(e.Data);
                Logger.Error("FxCopCmd: {0}", e.Data);
            };

            process.Start();
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();
            process.WaitForExit();

            var errorCode = (FxCopErrorCodes)process.ExitCode;

            if (errorCode.HasFlag(FxCopErrorCodes.AnalysisError))
            {
                var message = Text.Format("FxCop returns error code '{0}'.", process.ExitCode);
                throw new ExecutionException(ExecutionException.FxCopFailed, message);
            }

            return 0;
        }

        internal static Solution BuildUpSolution(string[] args)
        {
            if (!Array.Exists(args, o => o.StartsWith("/s:", StringComparison.OrdinalIgnoreCase)))
            {
                throw new ExecutionException(ExecutionException.SolutionDirIsNotSpecified,
                    "Failed to perform code analysis because /s options isn't specified.");
            }

            var solutionDir = args.First(o => o.StartsWith("/s:", StringComparison.OrdinalIgnoreCase)).Substring(3);
            return new Solution(solutionDir);
        }

        internal static Project BuildUpProject(string[] args)
        {
            if (!Array.Exists(args, o => o.StartsWith("/p:", StringComparison.OrdinalIgnoreCase)))
            {
                throw new ExecutionException(ExecutionException.ProjectDirIsNotSpecified,
                    "Failed to perform code analysis because /p options isn't specified.");
            }
            if (!Array.Exists(args, o => o.StartsWith("/f:", StringComparison.OrdinalIgnoreCase)))
            {
                throw new ExecutionException(ExecutionException.AssemblyPathIsNotSpecified,
                    "Failed to perform code analysis because /f options isn't specified.");
            }

            var projectDir = args.First(o => o.StartsWith("/p:", StringComparison.OrdinalIgnoreCase)).Substring(3);
            var assemblyPath = args.First(o => o.StartsWith("/f:", StringComparison.OrdinalIgnoreCase)).Substring(3);
            var dependencies = args.Where(o => o.StartsWith("/d:", StringComparison.OrdinalIgnoreCase)).Select(o => o.Substring(3)).ToArray();
            return new Project(projectDir, assemblyPath, dependencies);
        }

        private static int Main(string[] args)
        {
            try
            {
                var program = new Program();
                return program.Execute(args);
            }
            catch (ExecutionException ex)
            {
                Console.Error.WriteLine(String.Join(Environment.NewLine, ex.Message, "", Help));
                logger.TraceEvent(TraceEventType.Error, ex.ErrorCode, Text.Format(ex));
                logger.Flush();
                return ex.ErrorCode;
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(String.Join(Environment.NewLine, "Unknown error occurred.", "", Help));
                var errorCode = ExecutionException.HasError | ExecutionException.UnknownError;
                logger.TraceEvent(TraceEventType.Error, errorCode, Text.Format(ex));
                logger.Flush();
                return errorCode;
            }
        }
    }
}
